package handler

import (
	"context"
	"crypto/sha512"
	"fmt"
	password "github.com/anaskhan96/go-password-encoder"
	"github.com/golang/protobuf/ptypes/empty"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"mxshop_srvs/user_srv/global"
	"mxshop_srvs/user_srv/model"
	"mxshop_srvs/user_srv/proto"
	"strings"
	"time"
)

type UserServer struct{}

func ModelToResponse(user model.User) *proto.UserInfoResponse {

	userInfoRsp := proto.UserInfoResponse{
		Id:       uint32(user.ID),
		Mobile:   user.Mobile,
		Password: user.Password,
		NickName: user.NickName,
		Gender:   user.Gender,
		Role:     uint32(user.Role),
	}

	//在grpc的message中字段有默认值，不能随便赋值nil进去，容易出错
	//要搞清楚，那些字段是有默认值
	if user.Birthday != nil {
		userInfoRsp.Birthday = uint64(user.Birthday.Unix())
	}
	return &userInfoRsp
}

func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {

		if page <= 0 {
			page = 1
		}

		switch {
		case pageSize > 100:
			pageSize = 100
		case pageSize <= 0:
			pageSize = 10
		}

		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

func GetNewPassword(pwd string) string {

	options := &password.Options{16, 100, 32, sha512.New}
	salt, encodePwd := password.Encode(pwd, options)
	newPassword := fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodePwd)
	return newPassword
}

func GetPasswordOptions() *password.Options {
	return &password.Options{16, 100, 32, sha512.New}
}

func (s *UserServer) GetUserList(ctx context.Context, req *proto.PageInfo) (*proto.UserListResponse, error) {

	var users []model.User
	result := global.DB.Find(&users)
	if result.Error != nil {
		return nil, result.Error
	}
	var rsp = &proto.UserListResponse{}
	rsp.Total = int32(result.RowsAffected)

	global.DB.Scopes(Paginate(int(req.Pn), int(req.PSize))).Find(&users)

	for _, user := range users {
		userInfoResp := ModelToResponse(user)
		rsp.Data = append(rsp.Data, userInfoResp)
	}
	return rsp, nil
}

func (s *UserServer) GetUserByMobile(ctx context.Context, req *proto.MobileRequest) (*proto.UserInfoResponse, error) {

	var user model.User
	result := global.DB.Where(&model.User{Mobile: req.Mobile}).First(&user)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "用户不存在")
	}
	if result.Error != nil {
		return nil, result.Error
	}
	userResp := ModelToResponse(user)
	return userResp, nil
}

func (s *UserServer) GetUserById(ctx context.Context, req *proto.IdRequest) (*proto.UserInfoResponse, error) {

	var user model.User
	result := global.DB.First(&user, req.Id)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "用户不存在")
	}
	if result.Error != nil {
		return nil, result.Error
	}
	userResp := ModelToResponse(user)
	return userResp, nil
}

func (s *UserServer) CreateUser(ctx context.Context, req *proto.CreateUserInfo) (*proto.UserInfoResponse, error) {

	//查询用户是否存在
	var user model.User
	result := global.DB.Where(&model.User{Mobile: req.Mobile}).First(&user)

	if result.RowsAffected == 1 {
		return nil, status.Error(codes.Internal, "用户已存在")
	}

	pwd := GetNewPassword(req.Password)
	user = model.User{
		Mobile:   req.Mobile,
		NickName: req.NickName,
		Password: pwd,
	}

	result = global.DB.Create(&user)
	if result.Error != nil {
		return nil, status.Error(codes.Internal, result.Error.Error())
	}
	userResp := ModelToResponse(user)
	return userResp, nil
}

func (s *UserServer) UpdateUser(ctx context.Context, req *proto.UpdateUserInfo) (*emptypb.Empty, error) {

	var user model.User
	result := global.DB.First(&user, req.Id)
	if result.RowsAffected == 0 {
		return nil, status.Error(codes.NotFound, "用户不存在")
	}
	//查询用户是否存在
	birthday := time.Unix(int64(req.BirthDay), 0)

	result = global.DB.Model(&user).Updates(model.User{NickName: req.NickName, Gender: req.Gender, Birthday: &birthday})
	if result.Error != nil {
		return &empty.Empty{}, status.Error(codes.Internal, result.Error.Error())
	}
	return &empty.Empty{}, nil
}

func (s *UserServer) CheckPassword(ctx context.Context, req *proto.PasswordCheckInfo) (*proto.CheckResponse, error) {

	encryptPwd := strings.Split(req.EncryptedPassword, "$")
	verifyResult := password.Verify(req.Password, encryptPwd[2], encryptPwd[3], GetPasswordOptions())

	return &proto.CheckResponse{
		Success: verifyResult,
	}, nil
}
